/**
 * Copyright (C) 2008 Wideplay Interactive.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.neodatis.warp;

import static com.wideplay.warp.util.Text.empty;

import java.lang.annotation.Annotation;

import org.aopalliance.intercept.MethodInterceptor;
import org.neodatis.odb.Configuration;
import org.neodatis.odb.ODB;
import org.neodatis.odb.ODBServer;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import com.wideplay.warp.persist.AbstractPersistenceModule;
import com.wideplay.warp.persist.InternalWorkManager;
import com.wideplay.warp.persist.PersistenceConfiguration;
import com.wideplay.warp.persist.PersistenceModule;
import com.wideplay.warp.persist.PersistenceModuleVisitor;
import com.wideplay.warp.persist.PersistenceService;
import com.wideplay.warp.persist.PersistenceStrategy;
import com.wideplay.warp.persist.PersistenceStrategyBuilder;
import com.wideplay.warp.persist.WorkManager;
import com.wideplay.warp.util.Text;

/**
 * @author Robbie Vanbrabant
 */
public class NeodatisPersistenceStrategy implements PersistenceStrategy {
    private final NeodatisSettings neodatisSettings;
    private final Class<? extends Annotation> annotation;

    private NeodatisPersistenceStrategy(NeodatisSettings neodatisSettings, Class<? extends Annotation> annotation) {
        this.neodatisSettings = neodatisSettings;
        this.annotation = annotation;
    }

    public PersistenceModule getBindings(final PersistenceConfiguration config) {
        return new NeodatisPersistenceModule(config);
    }

    class NeodatisPersistenceModule extends AbstractPersistenceModule {
        private final PersistenceConfiguration config;
        private final PersistenceService pService;
        private final WorkManager workManager;
        private final Provider<ODB> odbp;
        private final ODBServerProvider osp;
        private InternalWorkManager<ODB> iwm;

        NeodatisPersistenceModule(PersistenceConfiguration config) {
            super(annotation);
            this.config = config;
            this.osp = new ODBServerProvider(neodatisSettings);
            iwm = new NeodatisInternalWorkManager(osp);
            this.odbp = new ODBProvider(iwm);
            this.pService = new NeodatisPersistenceService(osp);
            this.workManager = new NeodatisWorkManager(iwm);
        }

        protected void configure() {
            // Set up Neodatis
            bindSpecial(ODBServer.class).toProvider(osp);
            
            bindSpecial(ODB.class).toProvider(odbp);
            bindSpecial(PersistenceService.class).toInstance(pService);
            bindSpecial(WorkManager.class).toInstance(workManager);

            MethodInterceptor txInterceptor = new NeodatisLocalTxnInterceptor(iwm, config.getUnitOfWork());
            bindTransactionInterceptor(config, txInterceptor);

            // No Dynamic Finders yet.
        }

        public void visit(PersistenceModuleVisitor visitor) {
            if (unitOfWorkRequest(config)) {
                visitor.publishWorkManager(this.workManager);
                visitor.publishPersistenceService(this.pService);
            }
        }
    }

    public static NeodatisPersistenceStrategyBuilder builder() {
        return new NeodatisPersistenceStrategyBuilder();
    }

    // Do not change to be non-static or Guice 2.0 will not be able to inject it.
    public static class NeodatisPersistenceStrategyBuilder implements PersistenceStrategyBuilder<NeodatisPersistenceStrategy> {
        private boolean untouched = true;
        private String databaseFileName;
        private Configuration configuration;
        private String host;
        private String password;
        private int port;
        private String user;
        private Class<? extends Annotation> annotation;

        @Inject(optional = true)
        public NeodatisPersistenceStrategyBuilder databaseFileName(@Neodatis String databaseFileName) {
            this.databaseFileName = databaseFileName;

            Text.nonEmpty(databaseFileName, "Neodatis ODB database file name was not set; please use " +
                    ".databaseFileName() with a string containing the name of a Neodatis ODB database file.");
            this.untouched = false;
            return this;
        }

        @Inject(optional = true)
        public NeodatisPersistenceStrategyBuilder configuration(Configuration configuration) {
            this.configuration = configuration;
            this.untouched = false;
            return this;
        }

        @Inject(optional = true)
        public NeodatisPersistenceStrategyBuilder host(@Named(Neodatis.HOST)String host) {
            this.host = host;

            Text.nonEmpty(host, "Please specify a valid host name.");
            this.untouched = false;
            return this;
        }

        @Inject(optional = true)
        public NeodatisPersistenceStrategyBuilder password(@Named(Neodatis.PASSWORD)String password) {
            this.password = password;
            this.untouched = false;
            return this;
        }

        @Inject(optional = true)
        public NeodatisPersistenceStrategyBuilder port(@Named(Neodatis.PORT)int port) {
            if (port < 0 || port > 65535)
                throw new IllegalArgumentException("Port number was invalid (must be in range 0-65535). Was: "
                        + port);

            this.port = port;
            this.untouched = false;
            return this;
        }

        @Inject(optional = true)
        public NeodatisPersistenceStrategyBuilder user(@Named(Neodatis.USER)String user) {
            this.user = user;
            this.untouched = false;
            return this;
        }

        public NeodatisPersistenceStrategyBuilder annotatedWith(Class<? extends Annotation> annotation) {
            this.annotation = annotation;
            this.untouched = false;
            return this;
        }

        // internal use only
        NeodatisSettings buildNeodatisSettings() {
            HostKind hostKind;
            if (databaseFileName == null) {
                if (!empty(host)) {
                    hostKind = HostKind.REMOTE;
                } else {
                    throw new IllegalStateException("Must specify either database file name: " +
                            "bindConstant().annotatedWith(Neodatis.class); or a remote server host: bindConstant()" +
                            ".annotatedWith(Names.named(Neodatis.HOST)).to(\"localhost\")");
                }
            } else if (!empty(host)) {
                hostKind = HostKind.LOCAL;
            } else {
                hostKind = HostKind.FILE;
            }
            return new NeodatisSettings(user, password, host, port, configuration, hostKind, databaseFileName);
        }

        public NeodatisPersistenceStrategy build() {
            // TODO validate more state
            return new NeodatisPersistenceStrategy(untouched ? null : buildNeodatisSettings(), this.annotation);
        }
    }
}
